#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Data;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Allows conversions between various .net data structures and Yarr types
	/// </summary>
	internal static class Conversions
	{
		/// <summary>
		/// The is Cast on steroids
		/// </summary>
		/// <param name="type">The type to cast to</param>
		/// <param name="value">The value to be cast</param>
		/// <returns></returns>
		public static object The (Type type, object value) 
		{
            if (type == null)
                return null;

            if (value == null)
            {
                if (type.IsValueType || type.IsEnum)
                    throw new YarrException("The: cannot create null value type");

                return null;
            }

            if (value is CastInstance)
                value = ((CastInstance)value).Value;

            if (value.GetType().Equals(type))
                return value;

            if (type == typeof(Symbol))
            {
                if (value is String && !(String.IsNullOrEmpty((string)value)))
                    return Symbol.FromName((string)value);
                else
                    throw new YarrException("The: can only create symbol from string");
            }

            if (type == typeof(Cons))
            {
			    // String to List
			    if (value is System.String)
				    return Cons.FromString(value as string);

                // Array to List
                if (value is System.Array)
                    return Cons.FromArray(value as Array);

                // Hashtable to List
                if (value is System.Collections.Hashtable)
                    return Cons.FromHashtable((Hashtable)value);

                // SortedList to List
                if (value is System.Collections.SortedList)
                    return Cons.FromSortedList((SortedList)value);

                // DataTable to List
                if (value is System.Data.DataTable)
                    return Cons.FromDataTable((System.Data.DataTable)value);

                // DataRow to List
                if (value is System.Data.DataRow)
                    return Cons.FromDataRow((DataRow)value);

                // Collection to List
                if (value is ICollection)
                    return Cons.FromICollection((ICollection)value);

                // Any IEnumerable to List
                if (value is IEnumerable)
                    return Cons.FromIEnumerable((IEnumerable)value);
            }

            if (value is Cons)
            {
                // List to String
                if (type == typeof(String))
                    return Conversions.ConsToString(value);

                // List to Array
                if (type == typeof(Array))
                    return ((Cons)value).ToArray();

                // List to Hashtable
                if (type == typeof(Hashtable))
                    return Conversions.ConsToHashtable(value);

                // List to Stack
                if (type == typeof(Stack))
                    return Conversions.ConsToStack(value);

                // List to Queue
                if (type == typeof(Queue))
                    return Conversions.ConsToQueue(value);

                // List to SortedList
                if (type == typeof(SortedList))
                    return Conversions.ConsToSortedList(value);
            }

            if (type == typeof(Type))
            {
                if (value is Symbol)
                    value = ((Symbol)value).Name;

                if (value is String)
                {
                    Type t = TypeCache.FindType((String)value);
                    if (t == null)
                        throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                            "The: Cannot convert {0} to {1}", value, type));
                    return t;
                }
            }

			// Anything to String
			if (type == typeof(String))
				return value.ToString();

			// Anything to Boolean true or false
			if (type == typeof(Boolean))
				return ObjectToBoolean(value);

			// Allow upcasting for types  
            if (Primitives.IsTypeOf(type, value))
            {
                if (Primitives.TypeOf(value) != type)
                    return new CastInstance(type, value);
                else
                    return value;
            }

            // Look for op_explicit
            Type[] typeArray = new Type[1];
            typeArray[0] = Primitives.TypeOf(value);
            MethodInfo mi = MethodCache.FindMethod("op_explicit", type, typeArray, null,
                                                        BindingFlags.IgnoreCase |
                                                        BindingFlags.Public |
                                                        BindingFlags.Static |
                                                        BindingFlags.FlattenHierarchy);

            if (mi != null)
            {
                Object[] arg = new Object[1];
                arg[0] = value;
                return mi.Invoke(type, arg);
            }
            else
            {
            // No explicit cast; look for op_implicit
                Object retVal;
                if (TryImplicitConversion(value, type, out retVal))
                    return retVal;
            }

			// Revert to standard type cast
			return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);			
		}


        public static bool TryImplicitConversion (object source, Type destType, out object result)
        {
            if (source == null)
            {
                if (destType.IsValueType || destType.IsEnum)
                {
                    result = null;
                    return false;
                }
                else
                {
                    result = null;
                    return true;
                }
            }

            Type sourceType = Primitives.TypeOf(source);
            if ( sourceType == destType)
            {
                result = source;
                return true;
            }

            Type[] typeArray = new Type[1];
            typeArray[0] = sourceType;
            MethodInfo mi = MethodCache.FindMethod("op_implicit", destType, typeArray, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);

            if (mi == null)
            {
                result = null;
                return false;
            }

            Object[] arg = new Object[1];
            arg[0] = source;
            result = mi.Invoke(destType, arg);
            return true;
        }


		/// <summary>
		/// Converts any Yarr form (actually any object) to a
		/// Boolean true or false.
		/// </summary>
		/// <param name="arg"></param>
		/// <returns></returns>
		public static bool ObjectToBoolean(Object arg) 
		{
            if (arg == null)
				return false;

            TypeCode argType = Type.GetTypeCode(arg.GetType());

            if (argType == TypeCode.DBNull)
                return false;

			// booleans evaluate to themselves
			if (argType == TypeCode.Boolean)
				return (bool)arg;

			// Empty strings are false
			if (argType == TypeCode.String && (String.IsNullOrEmpty((string)arg)))
				return false;

            // By convention, DateTime.MinValue is considered false
            if (argType == TypeCode.DateTime  &&  ((DateTime)arg).Equals(DateTime.MinValue))
                return false;

            // Other non-numerics are true
            if (argType == TypeCode.DateTime || argType == TypeCode.Object || argType == TypeCode.String)
                return true;

            // numeric types -- zero evaluates to false, everything else to true
            switch (argType)
            {
                case TypeCode.Byte:
                case TypeCode.Char:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                    return Convert.ToInt64(arg) != (Int64)0;

                case TypeCode.UInt64:
                    return (UInt64)arg != (UInt64)0;

                case TypeCode.Decimal:
                    return (Decimal)arg != (Decimal)0.0;

                case TypeCode.Single:
                case TypeCode.Double:
                    return Convert.ToDouble(arg) != (Double)0.0;

                default:
                    return true;
            }
		}


		/// <summary>
		/// Converts a list to a string
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static string ConsToString(Object o) 
		{
			if (o == null)
				return String.Empty;
			else 
			{
				StringBuilder stringBuilder = new StringBuilder();
			
				stringBuilder.Append(((Cons)o).Car());
				stringBuilder.Append(ConsToString(((Cons)o).Cdr()));
				return stringBuilder.ToString();
			}

		}


		/// <summary>
		/// Converts a list to a Queue
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static Queue ConsToQueue(Object o) 
		{
	
			Queue queue = new Queue();

			Cons temp = (Cons)o;
			while (temp != null) 
			{
				queue.Enqueue(temp.First());
				temp = (Cons)temp.Rest();
			}
			return queue;
		}


		/// <summary>
		/// Returns a new Stack poluated with data items from the given
		/// Yarr.List object
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static Stack ConsToStack(Object o) 
		{
	
			Stack stack = new Stack();

			// null signifies an empty stack
			if (o == null)
				return stack;

			Cons temp = (Cons)o; 
			while (temp!= null) 
			{
				stack.Push(temp.First());
				temp = (Cons)temp.Rest();
			}
			return stack;
		}


		/// <summary>
		/// Converts a list to a SortedList
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static SortedList ConsToSortedList(Object o) 
		{
			SortedList sortedList = new SortedList();

			Cons temp = (Cons)o;
			while (temp!= null) 
			{
				Cons element = (Cons)temp.First();
				sortedList.Add(element.First(), element.Second());
				temp = (Cons)temp.Rest();
			}
			return sortedList;
		}


		/// <summary>
		/// Converts a list to a Hashtable
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static Hashtable ConsToHashtable(Object o) 
		{
			Hashtable hashtable = new Hashtable();

			Cons temp = (Cons)o;
			while (temp != null) 
			{
				Cons element = (Cons)temp.First();

				hashtable[element.First()] = element.Second();
				temp = (Cons)temp.Rest();
			}
			return hashtable;
		}
	}
}
